package com.csjy.migration.hnnd;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 迁移入口
 *
 *  迁移原则
 *  - 按项目进行迁移，可指定单个或多个项目进行迁移(用逗号进行连接)
 *  - 每插入一条任意表记录，都记录至MIGRATION_ID_MAP表
 *  - 基础数据插入不使用事务
 *  - 保留原数据表的id字段
 *  - 一个表对应一个迁移类？
 *  - 字典?
 *  - 机构id?
 *  - 人员id?
 *  - 日志已项目为单位进行记录
 *
 *  相关参数
 *  - projectIds 项目id
 *  - cover 是否覆盖迁移，默认不覆盖（0），即：已迁移的不会重复迁移
 *  - part  迁移部分，默认all，可取值：all(全部)、business(基础数据)、workflow(流程数据)
 *  - workflowIds 流程id，若指定了该参数，则表示仅迁移指定流程
 */
public class Main {
  public static DbUtil targetDbUtil = null;
  public static DbUtil sourceDbUtil = null;
  public static ProgramArgument programArgument = null;
  public static FileWriter logFw = null;

  private static Integer migratedSuccessedCount = 0;
  private static Integer migratedFailuredCount = 0;
  private static List<String> migratedFailureProjectIdList = new ArrayList<>();

  public synchronized static Integer getMigratedSuccessedCount() {
    return migratedSuccessedCount;
  }

  public synchronized static void incrementMigratedSuccessedCount() {
    Main.migratedSuccessedCount ++;
  }

  public synchronized static Integer getMigratedFailuredCount() {
    return migratedFailuredCount;
  }

  public synchronized static void incrementMigratedFailuredCount() {
    Main.migratedFailuredCount ++ ;
  }

  public static void main(String[] args) throws SQLException, ClassNotFoundException, IOException {
    // ========打印帮助信息=========//
    printHelpMsg();
    if(args.length > 0 && (args[0].equals("help") || args[0].equals("-help") || args[0].equals("--help"))) {
      System.exit(0);
    }

    System.getProperties().setProperty("oracle.jdbc.J2EE13Compliant", "true");

    // ======= 解析参数==========//
    programArgument = resolveArgs(args);


    System.out.println("========迁移准备========");

    long startTime = System.currentTimeMillis();

    // ====== 数据库连接测试======//
    Map<String,Map<String,String>> dbSettingMap = null;
    try {
      System.out.println("数据库连接测试...");
      dbSettingMap = testConn();
      System.out.println("数据库连接测试成功.");
    } catch(Exception e) {
      System.out.println("数据库连接异常,原因:");
      e.printStackTrace();
      System.exit(1);
    }

    sourceDbUtil = new DbUtil(dbSettingMap.get("sourceJdbcUrl"));
    targetDbUtil = new DbUtil(dbSettingMap.get("targetJdbcUrl"));

    if(!programArgument.getOrgCodes().equals("")) {
      if(programArgument.getOrgCodes().contains("R")){
        new ForbiddenPosition(sourceDbUtil).start(programArgument);
        System.exit(0);
      }
    }
    // ==========创建日志文件对象==========//
    try {
      System.out.println("创建全局日志文件对象...");
      logFw = createLogFileInstance();
      System.out.println("全局日志文件对象创建成功.");
    } catch (IOException e) {
      System.out.println("全局日志文件对象创建失败,原因:");
      e.printStackTrace();
      System.exit(1);
    }

    // ==========开始迁移基础数据===================//
    if(programArgument.getMigrateCommonData().equals("1")){
      CommonDataMain commonDataMain = new CommonDataMain(sourceDbUtil, targetDbUtil, programArgument);
      commonDataMain.start();
    }


    // ==========开始迁移项目数据===================//
    migrate();

    // ============耗时打印==============//
    long endTime = System.currentTimeMillis();
    SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
    format.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));
    String usedTime = "共耗时:"+format.format(endTime - startTime);
    System.out.println(usedTime);
    Tool.writeLog(logFw,usedTime);

    // ===========释放资源===================//
    logFw.close();
    System.exit(0);

  }

  /**
   * 打印帮助信息
   */
  public static void printHelpMsg() {
    System.out.println("===========使用说明==========");
    System.out.println("迁移程序包含以下三个参数:");
    System.out.println();
    System.out.println("projectIds");
    System.out.println("  项目id，若指定多个，用逗号连接，例如，projectIds=16723,132323，表示仅迁移指定项目的数据");
    System.out.println();
    System.out.println("cover");
    System.out.println("  是否覆盖迁移，取值0或1，默认为0，0表示增量迁移(已迁移的不会重新迁移)，1表示重新迁移(已迁移的会先删除掉)");
    System.out.println();
    System.out.println("workflowIds");
    System.out.println("  流程id，若指定多个，用英文输入法状态下的逗号连接，例如，workflowIds=332323,488232，表示仅迁移指定流程的流程数据(若指定了该参数,则workflowCodes参数无效)");
    System.out.println();
    System.out.println("workflowCodes");
    System.out.println("  流程类型编码，若指定多个，用英文输入法状态下的逗号连接，例如，workflowCodes=item-accept-hny,item-audit-hny，表示仅迁移指定流程类型的流程数据");
    System.out.println();
    System.out.println("tableNames");
    System.out.println("  迁移的表名，若指定多个，用英文输入法状态下的逗号连接，例如，tableNames=acthiprocinst，表示仅迁移指定表的数据");
    System.out.println();
    System.out.println("excludeTableNames");
    System.out.println("  不迁移的表名，若指定多个，用英文输入法状态下的逗号连接，例如，excludeTableNames=acthiprocinst，表示不迁移指定表的数据(若同时指定了tableNames参数和本参数,本参数无效)");
    System.out.println();
    System.out.println();
    System.out.println("migrateCommonData");
    System.out.println("1: 迁移（默认 0：不迁移");
    System.out.println();
    System.out.println("threadCount");
    System.out.println("  多线程线程池大小");
    System.out.println();
    System.out.println("使用示例：java -jar csjy-data-migration-版本号.jar cover=1 projectIds=12334,1233");
  }

  /**
   * 解析程序参数
   * @param args
   */
  public static ProgramArgument resolveArgs(String[] args) {
    ProgramArgument programArgument = ProgramArgument.buildDefault();

    if(args == null) {
      return programArgument;
    }

    for(String arg: args) {
      String[] argNameAndValue = arg.split("=");
      if(argNameAndValue.length != 2) {
        continue;
      }
      String argName = argNameAndValue[0];
      String argValue = argNameAndValue[1];

      if(argName.equals("projectIds")){
        programArgument.setProjectIds(argValue);
      } else if(argName.equals("orgCodes")){
        programArgument.setOrgCodes(argValue);
      } else if(argName.equals("cover")){
        programArgument.setCover(argValue);
      } else if(argName.equals("workflowIds")) {
        programArgument.setWorkflowIds(argValue);
      } else if(argName.equals("tableNames")) {
        programArgument.setTableNames(argValue);
      } else if(argName.equals("excludeTableNames")) {
        programArgument.setExcludeTableNames(argValue);
      } else if(argName.equals("workflowCodes")) {
        programArgument.setWorkflowCodes(argValue);
      }else if(argName.equals("migrateCommonData")) {
        programArgument.setMigrateCommonData(argValue);
      }else if(argName.equals("threadCount")) {
        programArgument.setThreadCount(Integer.parseInt(argValue));
      }else if(argName.equals("onlyWorkflowData")) {
        programArgument.setOnlyWorkflowData(argValue);
      }else if(argName.equals("onlyMigrateUndoneWorkflow")) {
        programArgument.setOnlyMigrateUndoneWorkflow(argValue);
      }else if(argName.equals("coverUndoneWorkflow")) {
        programArgument.setCoverUndoneWorkflow(argValue);
      }else if(argName.equals("autoGenLoanInfo")) {
        programArgument.setAutoGenLoanInfo(argValue);
      }
    }
    return programArgument;
  }

  /**
   * 连接测试
   * @throws SQLException
   * @throws IOException
   */
  public static  Map<String,Map<String,String>> testConn() throws SQLException, IOException {
    String dbSettingFileName = "csjy-data-migration-setting.json";
    File dbSettingFile = new File(dbSettingFileName);
    ObjectMapper mapper = new ObjectMapper();
    Map<String,Map<String,String>> dbSettingMap = null;
    if(!dbSettingFile.exists()) {
      dbSettingMap = new HashMap();
      Map<String,String> jdbcConfigEmptyMap = new HashMap<>();
      jdbcConfigEmptyMap.put("url","jdbc:oracle:thin:JWFRAME/1@localhost:1521:orcl");
      jdbcConfigEmptyMap.put("username","");
      jdbcConfigEmptyMap.put("password","");
      jdbcConfigEmptyMap.put("driverClassName","oracle.jdbc.OracleDriver");
      dbSettingMap.put("sourceJdbcUrl",jdbcConfigEmptyMap);
      dbSettingMap.put("targetJdbcUrl",jdbcConfigEmptyMap);
      mapper.writeValue(dbSettingFile, dbSettingMap);
      System.out.println("数据库配置文件"+dbSettingFileName+"未找到,程序已自动创建,请将源数据库和目标数据库的连接串写入该文件后再试.");
      System.exit(1);
    }else {
      dbSettingMap = mapper.readValue(dbSettingFile,Map.class);
    }

    if(DbUtil.testConn(dbSettingMap.get("sourceJdbcUrl"))){
      System.out.println("源数据库连接测试成功。");
    }else {
      System.out.println("源数据库连接测试失败。");
      System.exit(1);
    }

    if(DbUtil.testConn(dbSettingMap.get("targetJdbcUrl"))){
      System.out.println("目标数据库连接测试成功。");
    }else {
      System.out.println("目标数据库连接测试失败。");
      System.exit(1);
    }

    return dbSettingMap;
  }

  /**
   * 创建日志文件实例
   */
  public static FileWriter createLogFileInstance() throws IOException {
    String folderName = "csjy-data-migration-logs";
    File folderFile = new File(folderName);
    if(!folderFile.exists()) {
      folderFile.mkdir();
    }
    String fileName = new java.sql.Timestamp(System.currentTimeMillis()).toString().replace(" ",".").replace(":","-");
    File migrationFailureRecordLogFile = new File(folderName + "\\" +fileName+"-migration-log.txt");
    migrationFailureRecordLogFile.createNewFile();
    return new FileWriter(migrationFailureRecordLogFile.getName(),true);
  }

  /**
   * 迁移
   */
  public static void migrate() throws SQLException, IOException {

    // 查询MigrationIdMap 表记录
    BusinessTool.init(sourceDbUtil,targetDbUtil);

    System.out.println("========开始迁移项目数据========");
    // 查询符合条件的项目列表
    String findProjectSql = "\tSELECT bib.ITEMNO ,bib.ITEMCODE ,beb.PRONAME \n" +
        "\tFROM BT_ITEM_BASIC bib \n" +
        "\tJOIN BT_ENTERPRISE_BASIC beb ON bib.ENTERPRISENO = beb.ENTERPRISENO \n" +
        "\tWHERE bib.ITEMCODE LIKE 'HND%'";

    List values = new ArrayList();
    if(null != programArgument.getProjectIds() && !programArgument.getProjectIds().equals("")) {
      String itemNos = "'"+Arrays.stream(programArgument.getProjectIds().split(",")).collect(Collectors.joining("','"))+"'";
      findProjectSql = findProjectSql + " AND bib.ITEMNO IN ("+itemNos+")";
    } else if(null != programArgument.getOrgCodes() && !programArgument.getOrgCodes().equals("")){
      String orgCodes = "'"+Arrays.stream(programArgument.getOrgCodes().split(",")).collect(Collectors.joining("','"))+"'";
      findProjectSql = findProjectSql + " AND bib.ORGCODE IN ("+orgCodes+")";
    } else if(programArgument.getOnlyMigrateUndoneWorkflow().equals("1")) {
      findProjectSql = findProjectSql + " AND bib.itemno IN (\n" +
          "select itemno from bt_item_workflow_rec\n" +
          "where workflowno in (select procinst_ from jbpm_taskinstance where isopen_ = 1)\n" +
          "group by itemno)";
    }
    findProjectSql = findProjectSql + " ORDER BY bib.CREATE_TIME  DESC ";

    List<Map> projectList = sourceDbUtil.selectByParams(findProjectSql, values);
    ExecutorService executorService = null;
    if(programArgument.getThreadCount() == 0) {
      executorService = Executors.newCachedThreadPool();
    }else{
      executorService = Executors.newFixedThreadPool(programArgument.getThreadCount());
    }


    for (Map project: projectList) {
      CompletableFuture.supplyAsync(()->{
        try {
          new ProjectMain(sourceDbUtil,targetDbUtil, programArgument, project.get("ITEMNO").toString()).start();
        }
        catch(InterruptedException interruptedException)
        {
          throw new RuntimeException(interruptedException);
        }
        catch(Exception e)
        {
          throw new RuntimeException(e);
        }
        return null;
      },executorService)
          .whenCompleteAsync((r,e)->{
        if(e == null) {
          Main.incrementMigratedSuccessedCount();
        } else {
          Main.incrementMigratedFailuredCount();
          Main.writeFailureLog(project,  e);
        }
      });
    }

    executorService.shutdown();

    while(projectList.size() != (Main.getMigratedSuccessedCount() + Main.getMigratedFailuredCount())) {

    }

    // 所有项目数量
    String findProjectCountSql = "SELECT COUNT(*) COUNT_ FROM BT_ITEM_BASIC bib WHERE bib.ITEMCODE LIKE 'HND%'";
    List<Map> projectCountList = sourceDbUtil.selectByParams(findProjectCountSql, Arrays.asList(new String[]{}));
    Object totalProjectCount = projectCountList.get(0).get("COUNT_");

    // 已成功迁移的项目数量
    String findMigratedProjectCountSql = "SELECT COUNT(*) COUNT_ FROM MIGRATION_ID_MAP WHERE SOURCE_ID LIKE '%JW%'";
    List<Map> migratedProjectCountList = targetDbUtil.selectByParams(findMigratedProjectCountSql, Arrays.asList(new String[]{}));
    Object migratedProjectCount = migratedProjectCountList.get(0).get("COUNT_");

    if(migratedFailureProjectIdList.size() > 0) {
      Tool.writeLog(logFw,"\n迁移失败的项目ID:"+ migratedFailureProjectIdList.stream().collect(Collectors.joining(","))+"\n");
    }

    // 记录整体迁移情况
    String migrateSummary = String.format("迁移情况：共检测到源数据库中共有%s个项目，本次迁移成功%s个，本次迁移失败%s个，累计迁移成功%s个."
            ,totalProjectCount,migratedSuccessedCount,migratedFailuredCount,migratedProjectCount);
    Tool.writeLog(logFw,"\n");
    Tool.writeLog(logFw,migrateSummary);

    System.out.println("========迁移完成，整体情况如下（迁移失败的具体原因请查阅日志文件）：========");
    System.out.println(migrateSummary);
  }

  /**
   * 写入项目迁移异常到日志文件
   */
  public synchronized static void writeFailureLog(Map project, Throwable e) {
    try {
      Tool.writeLog(logFw, String.format("项目[%s]-[%s]-[%s]迁移失败:\n",project.get("ITEMNO"), project.get("ITEMCODE"),project.get("PRONAME")));
      if(e instanceof InvocationTargetException) {
        Tool.writeLog(logFw, ((InvocationTargetException)e).getTargetException().getMessage()+"\n");
      } else {
        Tool.writeLog(logFw, e.getMessage()+"\n");
      }
      Tool.writeLog(logFw,"\n");
      migratedFailureProjectIdList.add(project.get("ITEMNO").toString());
    } catch (IOException ioException) {
      ioException.printStackTrace();
    }
  }

}
